﻿using System.Collections.Generic;
using System.Linq;
using Roslyn.Compilers.Common;
using Roslyn.Compilers.CSharp;
using RoslynHelper;

namespace ParameterizedNewGenericConstraint
{
    internal class ClassParameterizedNewGenericConstraintRewriter : SyntaxRewriter
    {
        private ISemanticModel _semanticModel;
        private Dictionary<string, List<TypeConstraintSyntax>> _typeConstraints = new Dictionary<string,List<TypeConstraintSyntax>>();
        private List<ParameterizedNewGenericConstraintAnnotation> _parameterizedNewGenericConstraintAnnotation;

        public ClassParameterizedNewGenericConstraintRewriter(ISemanticModel semanticModel, List<ParameterizedNewGenericConstraintAnnotation> newGenericConstraintAnnotations)
        {
            _semanticModel = semanticModel;
            _parameterizedNewGenericConstraintAnnotation = newGenericConstraintAnnotations;
        }

        protected override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
        {
            var value = base.VisitClassDeclaration(node);
            if (_typeConstraints.Any())
            {
                ClassDeclarationSyntax classDeclaration = 
                    node.SetGenericConstraintClauses(((ClassDeclarationSyntax)value).ConstraintClauses)
                    .DefineMembers(
                        node.Members.Select(m => (MemberDeclarationSyntax)Visit(m)).Union(
                            _typeConstraints.Select(tc =>
                                Syntax.PropertyDeclaration(
                                    modifiers: Syntax.TokenList(Syntax.Token(SyntaxKind.PublicKeyword)),
                                    type: Syntax.QualifiedName(
                                        Syntax.IdentifierName("System"),
                                        right: Syntax.GenericName(
                                            Syntax.Identifier("Func"),
                                            Syntax.TypeArgumentList(
                                                arguments: SeparatedList.Create(tc.Value.Select(tct => tct.Type).
                                                    Union(new [] { Syntax.IdentifierName(tc.Key)}))))),
                                    identifier: Syntax.Identifier("Factory" + tc.Key),
                                    accessorList: Syntax.AccessorList(
                                        accessors: Syntax.List<AccessorDeclarationSyntax>(
                                            Syntax.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration,
                                                semicolonTokenOpt: Syntax.Token(SyntaxKind.SemicolonToken)),
                                            Syntax.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration,
                                                semicolonTokenOpt: Syntax.Token(SyntaxKind.SemicolonToken))))))));
                var parameterizedNewGenericConstraintAnnotation = new ParameterizedNewGenericConstraintAnnotation() { TypesConstraintSyntax = _typeConstraints };
                _parameterizedNewGenericConstraintAnnotation.Add(parameterizedNewGenericConstraintAnnotation);
                value = classDeclaration.WithAdditionalAnnotations(parameterizedNewGenericConstraintAnnotation);
                _typeConstraints = new Dictionary<string,List<TypeConstraintSyntax>>();
            }
            return value;
        }

        protected override SyntaxNode VisitTypeParameterConstraintClause(TypeParameterConstraintClauseSyntax node)
        {
            if (node.Constraints.Count < 2 || node.Constraints.FirstOrDefault() as ConstructorConstraintSyntax == null)
                return node;
            _typeConstraints.Add(node.Identifier.GetText(), node.Constraints.Skip(1).OfType<TypeConstraintSyntax>().ToList());
            return null;
        }

        protected override SyntaxNode VisitObjectCreationExpression(ObjectCreationExpressionSyntax node)
        {
            if (!_semanticModel.SyntaxTree.Root.DescendentNodes().Contains(node))
                return node;
            var typeSymbol = _semanticModel.GetSemanticInfo(node.Type).Symbol;
            if (typeSymbol == null || typeSymbol.Kind != CommonSymbolKind.TypeParameterType)
                return node;
            return Syntax.InvocationExpression(Syntax.IdentifierName("Factory" + typeSymbol.Name), node.ArgumentListOpt);
        }
    }
}
